﻿// Copyright © Microsoft Corporation. All Rights Reserved. 
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using smarx.BlobSync;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Net;

namespace smarx.BlobSync
{
    public class SyncAndRun
    {
        OneWayBlobSync sync;
        OneWayGitSync gitSync;
        string localPath;
        List<Process> processes;
        EventHandler restarter;
        int howMany;
        TimeSpan interval;
        IRunner[] runners = new IRunner[] { new Python(), new Ruby(), new Node() };

        public SyncAndRun(CloudBlobContainer container, string localPath, string gitUrl, TimeSpan interval, int howMany)
        {
            this.howMany = howMany;
            this.interval = interval;
            this.localPath = localPath;
            if (container != null)
            {
                sync = new OneWayBlobSync(container, localPath, interval);
                sync.SyncCompleted += new SyncCompletedHandler(sync_SyncCompleted);
            }
            if (!string.IsNullOrEmpty(gitUrl))
            {
                gitSync = new OneWayGitSync(gitUrl, localPath);
                gitSync.SyncCompleted += new SyncCompletedHandler(sync_SyncCompleted);
            }
            restarter = new EventHandler(Restart);
            processes = new List<Process>();
        }

        public void SyncOnce()
        {
            if (gitSync != null) gitSync.SyncAll();
            if (sync != null) sync.SyncAll();
        }

        public void SyncForever()
        {
            while (true)
            {
                SyncOnce();
                Thread.Sleep(interval);
            }
        }

        private IRunner GetRunner()
        {
            foreach (var runner in runners)
            {
                foreach (var file in runner.DetectionFiles)
                {
                    if (File.Exists(Path.Combine(localPath, file)))
                    {
                        return runner;
                    }
                }
            }
            return null;
        }

        private Process StartCommand(string command, string arguments, bool restart)
        {
            var proc = new Process
            {
                StartInfo = new ProcessStartInfo(command, arguments)
                {
                    UseShellExecute = false,
                    WorkingDirectory = localPath
                },
                EnableRaisingEvents = true
            };
            if (restart)
            {
                proc.Exited += restarter;
            }
            proc.Start();
            return proc;
        }

        private void Restart(object sender, EventArgs e)
        {
            ((Process)sender).Start();
        }

        private void RunCommand(string command, string arguments)
        {
            var proc = StartCommand(command, arguments, false);
            proc.WaitForExit(300000); // wait up to five minutes, then kill the process
            try { proc.Kill(); }
            catch { }
        }

        private Process Run(int port)
        {
            string extraArgs = string.Format("-p {0} -e production", port);
            var runner = GetRunner();
            if (runner != null)
            {
                var command = runner.RunCommand;
                var proc = StartCommand(command.Command, command.Arguments + " " + extraArgs, true);
                // wait for up to 60 seconds for the app to respond a success code to http://127.0.0.1:####/
                for (int tries = 0; tries < 12; tries++)
                {
                    try
                    {
                        new WebClient().DownloadString(string.Format("http://127.0.0.1:{0}", port));
                        return proc;
                    }
                    catch
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(5));
                    }
                }
            }
            return null;
        }

        void sync_SyncCompleted(object sender)
        {
            foreach (var runner in runners)
            {
                foreach (var command in runner.DependencyCommands)
                {
                    RunCommand(command.Command, command.Arguments);
                }
            }
            for (int i = 0; i < processes.Count; i++)
            {
                if (processes[i] != null)
                {
                    processes[i].Exited -= restarter;
                    try { processes[i].Kill(); }
                    catch { }
                    processes[i].WaitForExit();
                }
                if (i < howMany)
                {
                    processes[i] = Run(9000 + i);
                }
            }
            for (int i = processes.Count; i < howMany; i++)
            {
                processes.Add(Run(9000 + i));
            }
        }

        public static SyncAndRun Create() { return Create(null); }
        public static SyncAndRun Create(string[] args)
        {
            List<string> paths = new List<string>();
            string localPath = args != null && args.Length > 0 ? args[0] : Path.GetFullPath(".");
            string gitUrl = null;
            if (RoleEnvironment.IsAvailable)
            {
                paths.Add(Path.Combine(RoleEnvironment.GetLocalResource("Node").RootPath, "bin"));
                paths.Add(RoleEnvironment.GetLocalResource("Python").RootPath);
                paths.Add(Path.Combine(RoleEnvironment.GetLocalResource("Python").RootPath, "scripts"));
                paths.Add(Path.Combine(
                    Directory.EnumerateDirectories(RoleEnvironment.GetLocalResource("Ruby").RootPath, "ruby-*").First(),
                    "bin"));
                Environment.SetEnvironmentVariable("HOME", Path.Combine(RoleEnvironment.GetLocalResource("Ruby").RootPath, "home"));
                localPath = RoleEnvironment.GetLocalResource("App").RootPath;
                gitUrl = RoleEnvironment.GetConfigurationSettingValue("GitUrl");
            }
            paths.Add(Environment.ExpandEnvironmentVariables(@"%systemdrive%\python27"));
            paths.Add(Environment.ExpandEnvironmentVariables(@"%systemdrive%\python27\scripts"));
            Environment.SetEnvironmentVariable("TAR_OPTIONS", "--no-same-owner"); // from setenv.cmd
            if (RoleEnvironment.IsAvailable)
            {
                Environment.SetEnvironmentVariable("PATH", string.Join(";", paths.ToArray()));
            }
            else
            {
                Environment.SetEnvironmentVariable("PATH",
                    Environment.GetEnvironmentVariable("PATH") + ";" + string.Join(";", paths.ToArray()));
            }
            CloudBlobContainer container = null;
            if (RoleEnvironment.IsAvailable)
            {
                if (!string.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("ContainerName")))
                {
                    container = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"))
                        .CreateCloudBlobClient().GetContainerReference(RoleEnvironment.GetConfigurationSettingValue("ContainerName"));
                }
            }
            else
            {
                container = CloudStorageAccount.DevelopmentStorageAccount.CreateCloudBlobClient().GetContainerReference("app");
            }
            if (container != null)
            {
                container.CreateIfNotExist();
            }
            return new SyncAndRun(container, localPath, gitUrl, TimeSpan.FromSeconds(5),
                RoleEnvironment.IsAvailable
                    ? int.Parse(RoleEnvironment.GetConfigurationSettingValue("NumProcesses"))
                    : 1
                );
        }

        static void Main(string[] args)
        {
            Create(args).SyncForever();
        }
    }
}
